/*-------------------------------------------------------------------------
 *
 * like_support.c
 *	  对 LIKE、正则表达式和相关操作符的计划支持函数。
 *
 * 这些例程处理可以与索引扫描一起使用的操作符的特殊优化，
 * 即使它们在执行器的 indexscan 机制中不知名。关键思想是这些操作符允许我们推导出
 * 近似的 indexscan qual 子句，从而任何通过操作符子句的元组必须也满足更简单的 indexscan 条件。
 * 然后我们可以利用 indexscan 机制来避免扫描表中我们原本必须扫描的部分，
 * 同时将原始操作符作为 qpqual 条件以确保我们只交付想要的元组。 （本质上，我们将常规
 * 索引用作有损索引。）
 *
 * 我们正在做的一个示例是
 *			textfield LIKE 'abc%def'
 * 从中我们可以生成索引可扫描条件
 *			textfield >= 'abc' AND textfield < 'abd'
 * 这允许对 textfield 的索引进行高效扫描。
 * （实际上，字符集和排序规则问题使得将 LIKE 转换为索引扫描限制比想象的要困难得多...
 * 但这就是基本思想。）
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/like_support.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <math.h>

#include "access/htup_details.h"
#include "access/stratnum.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/pg_locale.h"
#include "utils/selfuncs.h"
#include "utils/varlena.h"


typedef enum
{
	Pattern_Type_Like,
	Pattern_Type_Like_IC,
	Pattern_Type_Regex,
	Pattern_Type_Regex_IC,
	Pattern_Type_Prefix
} Pattern_Type;

typedef enum
{
	Pattern_Prefix_None, Pattern_Prefix_Partial, Pattern_Prefix_Exact
} Pattern_Prefix_Status;

static Node *fc_like_regex_support(Node *fc_rawreq, Pattern_Type fc_ptype);
static List *fc_match_pattern_prefix(Node *fc_leftop,
								  Node *fc_rightop,
								  Pattern_Type fc_ptype,
								  Oid fc_expr_coll,
								  Oid fc_opfamily,
								  Oid fc_indexcollation);
static double fc_patternsel_common(PlannerInfo *fc_root,
								Oid fc_oprid,
								Oid fc_opfuncid,
								List *fc_args,
								int fc_varRelid,
								Oid fc_collation,
								Pattern_Type fc_ptype,
								bool fc_negate);
static Pattern_Prefix_Status fc_pattern_fixed_prefix(Const *fc_patt,
												  Pattern_Type fc_ptype,
												  Oid fc_collation,
												  Const **fc_prefix,
												  Selectivity *fc_rest_selec);
static Selectivity fc_prefix_selectivity(PlannerInfo *fc_root,
									  VariableStatData *fc_vardata,
									  Oid fc_eqopr, Oid fc_ltopr, Oid fc_geopr,
									  Oid fc_collation,
									  Const *fc_prefixcon);
static Selectivity fc_like_selectivity(const char *fc_patt, int fc_pattlen,
									bool fc_case_insensitive);
static Selectivity fc_regex_selectivity(const char *fc_patt, int fc_pattlen,
									 bool fc_case_insensitive,
									 int fc_fixed_prefix_len);
static int	fc_pattern_char_isalpha(char fc_c, bool fc_is_multibyte,
								 pg_locale_t fc_locale, bool fc_locale_is_c);
static Const *fc_make_greater_string(const Const *fc_str_const, FmgrInfo *fc_ltproc,
								  Oid fc_collation);
static Datum fc_string_to_datum(const char *fc_str, Oid fc_datatype);
static Const *fc_string_to_const(const char *fc_str, Oid fc_datatype);
static Const *fc_string_to_bytea_const(const char *fc_str, size_t fc_str_len);


/*
 * 规划器支持函数，用于LIKE、正则表达式和相关操作符
 */
Datum textlike_support(PG_FUNCTION_ARGS)
{
	Node	   *fc_rawreq = (Node *) PG_GETARG_POINTER(0);

	PG_RETURN_POINTER(fc_like_regex_support(fc_rawreq, Pattern_Type_Like));
}

Datum texticlike_support(PG_FUNCTION_ARGS)
{
	Node	   *fc_rawreq = (Node *) PG_GETARG_POINTER(0);

	PG_RETURN_POINTER(fc_like_regex_support(fc_rawreq, Pattern_Type_Like_IC));
}

Datum textregexeq_support(PG_FUNCTION_ARGS)
{
	Node	   *fc_rawreq = (Node *) PG_GETARG_POINTER(0);

	PG_RETURN_POINTER(fc_like_regex_support(fc_rawreq, Pattern_Type_Regex));
}

Datum texticregexeq_support(PG_FUNCTION_ARGS)
{
	Node	   *fc_rawreq = (Node *) PG_GETARG_POINTER(0);

	PG_RETURN_POINTER(fc_like_regex_support(fc_rawreq, Pattern_Type_Regex_IC));
}

Datum text_starts_with_support(PG_FUNCTION_ARGS)
{
	Node	   *fc_rawreq = (Node *) PG_GETARG_POINTER(0);

	PG_RETURN_POINTER(fc_like_regex_support(fc_rawreq, Pattern_Type_Prefix));
}

/* 上述内容的公共代码 */
static Node * fc_like_regex_support(Node *fc_rawreq, Pattern_Type fc_ptype)
{
	Node	   *fc_ret = NULL;

	if (IsA(fc_rawreq, SupportRequestSelectivity))
	{
		/*
		 * 为函数调用生成选择性估算，就像我们通过相应的操作符进行调用时所做的那样。
		 */
		SupportRequestSelectivity *fc_req = (SupportRequestSelectivity *) fc_rawreq;
		Selectivity fc_s1;

		if (fc_req->is_join)
		{
			/*
			 * 目前我们只是暂时搁置。如果patternjoinsel有任何改进以更好地执行，这应该调用它。
			 */
			fc_s1 = DEFAULT_MATCH_SEL;
		}
		else
		{
			/* 与操作符限制选择性函数共享代码 */
			fc_s1 = fc_patternsel_common(fc_req->root,
								   InvalidOid,
								   fc_req->funcid,
								   fc_req->args,
								   fc_req->varRelid,
								   fc_req->inputcollid,
								   fc_ptype,
								   false);
		}
		fc_req->selectivity = fc_s1;
		fc_ret = (Node *) fc_req;
	}
	else if (IsA(fc_rawreq, SupportRequestIndexCondition))
	{
		/* 尝试将操作符/函数调用转换为索引条件 */
		SupportRequestIndexCondition *fc_req = (SupportRequestIndexCondition *) fc_rawreq;

		/*
		 * 当前，我们没有“反向”匹配操作符，模式在左侧，因此我们只需要考虑索引键在左侧的情况。
		 */
		if (fc_req->indexarg != 0)
			return NULL;

		if (is_opclause(fc_req->node))
		{
			OpExpr	   *fc_clause = (OpExpr *) fc_req->node;

			Assert(list_length(fc_clause->args) == 2);
			fc_ret = (Node *)
				fc_match_pattern_prefix((Node *) linitial(fc_clause->args),
									 (Node *) lsecond(fc_clause->args),
									 fc_ptype,
									 fc_clause->inputcollid,
									 fc_req->opfamily,
									 fc_req->indexcollation);
		}
		else if (is_funcclause(fc_req->node))	/* 过于谨慎 */
		{
			FuncExpr   *fc_clause = (FuncExpr *) fc_req->node;

			Assert(list_length(fc_clause->args) == 2);
			fc_ret = (Node *)
				fc_match_pattern_prefix((Node *) linitial(fc_clause->args),
									 (Node *) lsecond(fc_clause->args),
									 fc_ptype,
									 fc_clause->inputcollid,
									 fc_req->opfamily,
									 fc_req->indexcollation);
		}
	}

	return fc_ret;
}

/*
 * match_pattern_prefix
 *	  尝试为LIKE或正则表达式操作符生成索引条件。
 */
static List * fc_match_pattern_prefix(Node *fc_leftop,
					 Node *fc_rightop,
					 Pattern_Type fc_ptype,
					 Oid fc_expr_coll,
					 Oid fc_opfamily,
					 Oid fc_indexcollation)
{
	List	   *fc_result;
	Const	   *fc_patt;
	Const	   *fc_prefix;
	Pattern_Prefix_Status fc_pstatus;
	Oid			fc_ldatatype;
	Oid			fc_rdatatype;
	Oid			fc_eqopr;
	Oid			fc_ltopr;
	Oid			fc_geopr;
	Oid			fc_preopr = InvalidOid;
	bool		fc_collation_aware;
	Expr	   *fc_expr;
	FmgrInfo	fc_ltproc;
	Const	   *fc_greaterstr;

	/*
	 * 对于非常量或NULL模式参数，无法执行任何操作。
	 *
	 * 注意，由于我们限制自己只考虑右侧是硬常量的情况，因此它是伪常量，我们不需要担心验证这一点。
	 */
	if (!IsA(fc_rightop, Const) ||
		((Const *) fc_rightop)->constisnull)
		return NIL;
	fc_patt = (Const *) fc_rightop;

	/*
	 * 如果表达式排序不确定，则不支持。优化后的相等或前缀测试使用字节比较，这与不确定的排序不一致。实际的模式匹配实现函数在后面会抛出错误，指明不支持不确定的排序。 （我们也可以在这里抛出错误，但这样做后会得到更精确的错误信息。） （支持至少Pattern_Prefix_Exact应该是可能的，但只要实际的模式匹配实现不支持它就没有意义。）
	 *
	 * 对于像bytea这样的非排序感知数据类型，expr_coll未设置。
	 */
	if (fc_expr_coll && !get_collation_isdeterministic(fc_expr_coll))
		return NIL;

	/*
	 * 尝试从模式中提取固定前缀。
	 */
	fc_pstatus = fc_pattern_fixed_prefix(fc_patt, fc_ptype, fc_expr_coll,
								   &fc_prefix, NULL);

	/* 如果没有固定前缀则失败 */
	if (fc_pstatus == Pattern_Prefix_None)
		return NIL;

	/*
	 * 根据左侧参数的类型确定我们想使用的操作符。通常这些只是类型的常规比较操作符，但如果我们考虑某个半遗留的“模式”操作类，则使用“模式”操作符。那些对排序不敏感，但总是使用C排序，正如我们想要的。选择的操作符还决定了前缀常量所需的类型。
	 */
	fc_ldatatype = exprType(fc_leftop);
	switch (fc_ldatatype)
	{
		case TEXTOID:
			if (fc_opfamily == TEXT_PATTERN_BTREE_FAM_OID)
			{
				fc_eqopr = TextEqualOperator;
				fc_ltopr = TextPatternLessOperator;
				fc_geopr = TextPatternGreaterEqualOperator;
				fc_collation_aware = false;
			}
			else if (fc_opfamily == TEXT_SPGIST_FAM_OID)
			{
				fc_eqopr = TextEqualOperator;
				fc_ltopr = TextPatternLessOperator;
				fc_geopr = TextPatternGreaterEqualOperator;
				/* 此操作族直接支持前缀 */
				fc_preopr = TextPrefixOperator;
				fc_collation_aware = false;
			}
			else
			{
				fc_eqopr = TextEqualOperator;
				fc_ltopr = TextLessOperator;
				fc_geopr = TextGreaterEqualOperator;
				fc_collation_aware = true;
			}
			fc_rdatatype = TEXTOID;
			break;
		case NAMEOID:

			/*
			 * 注意，这里我们需要右侧类型为text，以便比较值不会错误地截断为NAMEDATALEN。
			 */
			fc_eqopr = NameEqualTextOperator;
			fc_ltopr = NameLessTextOperator;
			fc_geopr = NameGreaterEqualTextOperator;
			fc_collation_aware = true;
			fc_rdatatype = TEXTOID;
			break;
		case BPCHAROID:
			if (fc_opfamily == BPCHAR_PATTERN_BTREE_FAM_OID)
			{
				fc_eqopr = BpcharEqualOperator;
				fc_ltopr = BpcharPatternLessOperator;
				fc_geopr = BpcharPatternGreaterEqualOperator;
				fc_collation_aware = false;
			}
			else
			{
				fc_eqopr = BpcharEqualOperator;
				fc_ltopr = BpcharLessOperator;
				fc_geopr = BpcharGreaterEqualOperator;
				fc_collation_aware = true;
			}
			fc_rdatatype = BPCHAROID;
			break;
		case BYTEAOID:
			fc_eqopr = ByteaEqualOperator;
			fc_ltopr = ByteaLessOperator;
			fc_geopr = ByteaGreaterEqualOperator;
			fc_collation_aware = false;
			fc_rdatatype = BYTEAOID;
			break;
		default:
			/* 除非我们附加到错误的操作符，否则无法到达这里 */
			return NIL;
	}

	/*
	 * 如果必要，将前缀常量转换为正确的类型。给定的前缀常量可以是text或bytea类型，因此我们唯一需要做的事情是将text转换为bpchar。这两种类型是二进制兼容的，因此重新标记Const节点就足够了。
	 */
	if (fc_prefix->consttype != fc_rdatatype)
	{
		Assert(fc_prefix->consttype == TEXTOID &&
			   fc_rdatatype == BPCHAROID);
		fc_prefix->consttype = fc_rdatatype;
	}

	/*
	 * 如果我们找到了一个精确匹配模式，生成一个“=”索引条件。
	 *
	 * 在这里和下面，检查所需的操作符是否实际上受到索引操作类的支持，如果不支持则安静失败。这使我们不必担心特定的操作类（除了遗留的“模式”情况）；任何正确实现操作符的索引都会工作。
	 */
	if (fc_pstatus == Pattern_Prefix_Exact)
	{
		if (!op_in_opfamily(fc_eqopr, fc_opfamily))
			return NIL;
		fc_expr = make_opclause(fc_eqopr, BOOLOID, false,
							 (Expr *) fc_leftop, (Expr *) fc_prefix,
							 InvalidOid, fc_indexcollation);
		fc_result = list_make1(fc_expr);
		return fc_result;
	}

	/*
	 * 否则，我们有一个非空的值前缀。一些操作类直接支持前缀检查，否则我们将尝试生成范围约束。
	 */
	if (OidIsValid(fc_preopr) && op_in_opfamily(fc_preopr, fc_opfamily))
	{
		fc_expr = make_opclause(fc_preopr, BOOLOID, false,
							 (Expr *) fc_leftop, (Expr *) fc_prefix,
							 InvalidOid, fc_indexcollation);
		fc_result = list_make1(fc_expr);
		return fc_result;
	}

	/*
	 * 由于我们需要范围约束，只有当索引对排序不敏感或具有“C”排序时，它才会可靠地工作。注意，这里我们查看的是索引的排序，而不是表达式的排序——这个测试*不*依赖于LIKE/正则操作符的排序。
	 */
	if (fc_collation_aware &&
		!lc_collate_is_c(fc_indexcollation))
		return NIL;

	/*
	 * 我们可以始终说“x >= prefix”。
	 */
	if (!op_in_opfamily(fc_geopr, fc_opfamily))
		return NIL;
	fc_expr = make_opclause(fc_geopr, BOOLOID, false,
						 (Expr *) fc_leftop, (Expr *) fc_prefix,
						 InvalidOid, fc_indexcollation);
	fc_result = list_make1(fc_expr);

	/*-------
	 * 如果我们可以创建比前缀更大的字符串，我们可以说“x < greaterstr”。注意：我们依赖make_greater_string()生成一个保证更大的字符串，而不仅仅是一个可能更大的字符串。一般来说，这只在C语言环境下保证，所以我们最好使用C语言环境索引排序。
	 *-------
	 */
	if (!op_in_opfamily(fc_ltopr, fc_opfamily))
		return fc_result;
	fmgr_info(get_opcode(fc_ltopr), &fc_ltproc);
	fc_greaterstr = fc_make_greater_string(fc_prefix, &fc_ltproc, fc_indexcollation);
	if (fc_greaterstr)
	{
		fc_expr = make_opclause(fc_ltopr, BOOLOID, false,
							 (Expr *) fc_leftop, (Expr *) fc_greaterstr,
							 InvalidOid, fc_indexcollation);
		fc_result = lappend(fc_result, fc_expr);
	}

	return fc_result;
}



/*
 * patternsel_common - 用于模式匹配限制选择性的通用代码。
 *
 * 为了支持通过操作符或函数路径使用这一点，调用者可以传入操作符 OID 或底层函数 OID；如有需要，我们会从前者查找后者。 （我们本可以让 patternsel() 调用 get_opcode()，但如果我们不需要与 pg_statistic 数据比较固定前缀，这项工作将是多余的。）
 *
 * 请注意，oprid 和/或 opfuncid 应该是针对正匹配操作符，即使 negate 为真。
 */
static double fc_patternsel_common(PlannerInfo *fc_root,
				  Oid fc_oprid,
				  Oid fc_opfuncid,
				  List *fc_args,
				  int fc_varRelid,
				  Oid fc_collation,
				  Pattern_Type fc_ptype,
				  bool fc_negate)
{
	VariableStatData fc_vardata;
	Node	   *fc_other;
	bool		fc_varonleft;
	Datum		fc_constval;
	Oid			fc_consttype;
	Oid			fc_vartype;
	Oid			fc_rdatatype;
	Oid			fc_eqopr;
	Oid			fc_ltopr;
	Oid			fc_geopr;
	Pattern_Prefix_Status fc_pstatus;
	Const	   *fc_patt;
	Const	   *fc_prefix = NULL;
	Selectivity fc_rest_selec = 0;
	double		fc_nullfrac = 0.0;
	double		fc_result;

	/*
	 * 根据操作符是匹配还是不匹配，将结果初始化为适当的默认估计。
	 */
	if (fc_negate)
		fc_result = 1.0 - DEFAULT_MATCH_SEL;
	else
		fc_result = DEFAULT_MATCH_SEL;

	/*
	 * 如果表达式不是变量 op 常量，则跳过并返回默认估计。
	 */
	if (!get_restriction_variable(fc_root, fc_args, fc_varRelid,
								  &fc_vardata, &fc_other, &fc_varonleft))
		return fc_result;
	if (!fc_varonleft || !IsA(fc_other, Const))
	{
		ReleaseVariableStats(fc_vardata);
		return fc_result;
	}

	/*
	 * 如果常量为 NULL，则假设操作符是严格的并返回零，即，
	 * 操作符将永远不会返回 TRUE。 （即使对于否定操作符也是零。）
	 */
	if (((Const *) fc_other)->constisnull)
	{
		ReleaseVariableStats(fc_vardata);
		return 0.0;
	}
	fc_constval = ((Const *) fc_other)->constvalue;
	fc_consttype = ((Const *) fc_other)->consttype;

	/*
	 * 右侧常量的类型为 text 或 bytea，适用于所有支持的操作符。
	 * 我们不期望在这里看到与二进制兼容的类型，因为常量折叠应已将常量重新标记为完全匹配操作符的声明类型。
	 */
	if (fc_consttype != TEXTOID && fc_consttype != BYTEAOID)
	{
		ReleaseVariableStats(fc_vardata);
		return fc_result;
	}

	/*
	 * 类似地，左侧暴露的类型应该是我们已知的那些之一。 
	 * （不要查看 vardata.atttype，它可能是某种二进制兼容但不同的类型。） 
	 * 我们可以利用它来识别比较运算符和所需的比较常数类型， 
	 * 就像在 match_pattern_prefix() 中一样。
	 */
	fc_vartype = fc_vardata.vartype;

	switch (fc_vartype)
	{
		case TEXTOID:
			fc_eqopr = TextEqualOperator;
			fc_ltopr = TextLessOperator;
			fc_geopr = TextGreaterEqualOperator;
			fc_rdatatype = TEXTOID;
			break;
		case NAMEOID:

			/*
			 * 注意，这里我们需要右侧类型为text，以便比较值不会错误地截断为NAMEDATALEN。
			 */
			fc_eqopr = NameEqualTextOperator;
			fc_ltopr = NameLessTextOperator;
			fc_geopr = NameGreaterEqualTextOperator;
			fc_rdatatype = TEXTOID;
			break;
		case BPCHAROID:
			fc_eqopr = BpcharEqualOperator;
			fc_ltopr = BpcharLessOperator;
			fc_geopr = BpcharGreaterEqualOperator;
			fc_rdatatype = BPCHAROID;
			break;
		case BYTEAOID:
			fc_eqopr = ByteaEqualOperator;
			fc_ltopr = ByteaLessOperator;
			fc_geopr = ByteaGreaterEqualOperator;
			fc_rdatatype = BYTEAOID;
			break;
		default:
			/* 除非我们附加到错误的操作符，否则无法到达这里 */
			ReleaseVariableStats(fc_vardata);
			return fc_result;
	}

	/*
	 * 获取 nullfrac 以供下面使用。
	 */
	if (HeapTupleIsValid(fc_vardata.statsTuple))
	{
		Form_pg_statistic fc_stats;

		fc_stats = (Form_pg_statistic) GETSTRUCT(fc_vardata.statsTuple);
		fc_nullfrac = fc_stats->stanullfrac;
	}

	/*
	 * 提取模式隐含的任何固定前缀，并估计模式其余部分的 
	 * 分数选择性。与许多其他选择性估计器不同，我们在此步骤中使用模式运算符的实际排序。 
	 * 这样做的原因不是因为我们期望排序对选择性估计有重要影响（通常没有）， 
	 * 而是因为我们希望确保在正确的缓存键下缓存已编译的正则表达式，以便在运行时可以重用。
	 */
	fc_patt = (Const *) fc_other;
	fc_pstatus = fc_pattern_fixed_prefix(fc_patt, fc_ptype, fc_collation,
								   &fc_prefix, &fc_rest_selec);

	/*
	 * 如果必要，将前缀常量强制转换为正确的类型。 
	 * 唯一需要处理的情况是将文本转换为 bpchar。 
	 * 这两种类型是二进制兼容的，因此重新标记 Const 节点就足够了。
	 */
	if (fc_prefix && fc_prefix->consttype != fc_rdatatype)
	{
		Assert(fc_prefix->consttype == TEXTOID &&
			   fc_rdatatype == BPCHAROID);
		fc_prefix->consttype = fc_rdatatype;
	}

	if (fc_pstatus == Pattern_Prefix_Exact)
	{
		/*
		 * 模式指定了精确匹配，因此按 '=' 的方式估计。
		 */
		fc_result = var_eq_const(&fc_vardata, fc_eqopr, fc_collation, fc_prefix->constvalue,
							  false, true, false);
	}
	else
	{
		/*
		 * 不是精确匹配模式。如果我们有一个足够大的直方图， 
		 * 通过计算直方图中的匹配来估计人口中直方图部分的选择性。 
		 * 如果没有，分别估计固定前缀和模式剩余部分的选择性，然后将二者结合 
		 * 以获得对直方图所代表的列人口部分的选择性估计。 
		 * （对于小直方图，我们结合这些方法。）
		 *
		 * 然后我们汇总任何最常见值的信息；这些不在直方图人口中， 
		 * 我们可以通过应用模式运算符得到精确答案，因此没有理由进行近似。 
		 * （如果 MCV 覆盖了总体人口的显著部分，这将大大提高准确性。）
		 */
		Selectivity fc_selec;
		int			fc_hist_size;
		FmgrInfo	fc_opproc;
		double		fc_mcv_selec,
					fc_sumcommon;

		/* 尝试使用直方图条目来获取选择性 */
		if (!OidIsValid(fc_opfuncid))
			fc_opfuncid = get_opcode(fc_oprid);
		fmgr_info(fc_opfuncid, &fc_opproc);

		fc_selec = histogram_selectivity(&fc_vardata, &fc_opproc, fc_collation,
									  fc_constval, true,
									  10, 1, &fc_hist_size);

		/* 如果条目少于100个，请使用启发式方法 */
		if (fc_hist_size < 100)
		{
			Selectivity fc_heursel;
			Selectivity fc_prefixsel;

			if (fc_pstatus == Pattern_Prefix_Partial)
				fc_prefixsel = fc_prefix_selectivity(fc_root, &fc_vardata,
											   fc_eqopr, fc_ltopr, fc_geopr,
											   fc_collation,
											   fc_prefix);
			else
				fc_prefixsel = 1.0;
			fc_heursel = fc_prefixsel * fc_rest_selec;

			if (fc_selec < 0)		/* 直方图条目少于10个？ */
				fc_selec = fc_heursel;
			else
			{
				/*
				 * 对于直方图大小在10到100之间的情况，我们结合 
				 * 直方图和启发式选择性，对更大的直方图尺寸给予 
				 * 越来越多的信任。
				 */
				double		fc_hist_weight = fc_hist_size / 100.0;

				fc_selec = fc_selec * fc_hist_weight + fc_heursel * (1.0 - fc_hist_weight);
			}
		}

		/* 无论如何，不要相信极小或极大的估计。 */
		if (fc_selec < 0.0001)
			fc_selec = 0.0001;
		else if (fc_selec > 0.9999)
			fc_selec = 0.9999;

		/*
		 * 如果我们有最常见值信息，计算满足 MCV OP PATTERN 的 MCV 
		 * 条目的比例。 这些比例直接贡献于结果选择性。 
		 * 还要汇总 MCV 条目所代表的总比例。
		 */
		fc_mcv_selec = mcv_selectivity(&fc_vardata, &fc_opproc, fc_collation,
									fc_constval, true,
									&fc_sumcommon);

		/*
		 * 现在合并来自 MCV 和直方图计算的结果， 
		 * 意识到直方图仅涵盖未列出的非空值。
		 */
		fc_selec *= 1.0 - fc_nullfrac - fc_sumcommon;
		fc_selec += fc_mcv_selec;
		fc_result = fc_selec;
	}

	/* 如果我们希望取非匹配而不是匹配，则进行调整 */
	if (fc_negate)
		fc_result = 1.0 - fc_result - fc_nullfrac;

	/* 结果应该在范围内，但确保... */
	CLAMP_PROBABILITY(fc_result);

	if (fc_prefix)
	{
		pfree(DatumGetPointer(fc_prefix->constvalue));
		pfree(fc_prefix);
	}

	ReleaseVariableStats(fc_vardata);

	return fc_result;
}

/*
 * 修复 SQL 可调用函数与 patternsel_common 之间的阻抗不匹配
 */
static double fc_patternsel(PG_FUNCTION_ARGS, Pattern_Type fc_ptype, bool fc_negate)
{
	PlannerInfo *fc_root = (PlannerInfo *) PG_GETARG_POINTER(0);
	Oid			fc_operator = PG_GETARG_OID(1);
	List	   *fc_args = (List *) PG_GETARG_POINTER(2);
	int			fc_varRelid = PG_GETARG_INT32(3);
	Oid			fc_collation = PG_GET_COLLATION();

	/*
	 * 如果这是针对 NOT LIKE 或类似运算符，则获取相应的 
	 * 正匹配运算符并据此工作。
	 */
	if (fc_negate)
	{
		fc_operator = get_negator(fc_operator);
		if (!OidIsValid(fc_operator))
			elog(ERROR, "patternsel called for operator without a negator");
	}

	return fc_patternsel_common(fc_root,
							 fc_operator,
							 InvalidOid,
							 fc_args,
							 fc_varRelid,
							 fc_collation,
							 fc_ptype,
							 fc_negate);
}

/*
 *		regexeqsel		- 正则表达式模式匹配的选择性。
 */
Datum regexeqsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternsel(fcinfo, Pattern_Type_Regex, false));
}

/*
 *		icregexeqsel	- 不区分大小写的正则表达式匹配选择性。
 */
Datum icregexeqsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternsel(fcinfo, Pattern_Type_Regex_IC, false));
}

/*
 *		likesel			- LIKE 模式匹配的选择性。
 */
Datum likesel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternsel(fcinfo, Pattern_Type_Like, false));
}

/*
 *		prefixsel			- 前缀运算符的选择性。
 */
Datum prefixsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternsel(fcinfo, Pattern_Type_Prefix, false));
}

/*
 *
 *		iclikesel			- ILIKE 模式匹配的选择性。
 */
Datum iclikesel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternsel(fcinfo, Pattern_Type_Like_IC, false));
}

/*
 *		regexnesel		- 正则表达式模式不匹配的选择性。
 */
Datum regexnesel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternsel(fcinfo, Pattern_Type_Regex, true));
}

/*
 *		icregexnesel	- 不区分大小写的正则表达式不匹配选择性。
 */
Datum icregexnesel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternsel(fcinfo, Pattern_Type_Regex_IC, true));
}

/*
 *		nlikesel		- LIKE 模式不匹配的选择性。
 */
Datum nlikesel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternsel(fcinfo, Pattern_Type_Like, true));
}

/*
 *		icnlikesel		- ILIKE 模式不匹配的选择性。
 */
Datum icnlikesel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternsel(fcinfo, Pattern_Type_Like_IC, true));
}

/*
 * patternjoinsel		- 模式匹配连接选择性的通用代码。
 */
static double fc_patternjoinsel(PG_FUNCTION_ARGS, Pattern_Type fc_ptype, bool fc_negate)
{
	/* 目前我们直接跳过。 */
	return fc_negate ? (1.0 - DEFAULT_MATCH_SEL) : DEFAULT_MATCH_SEL;
}

/*
 *		regexeqjoinsel	- 正则表达式模式匹配的连接选择性。
 */
Datum regexeqjoinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternjoinsel(fcinfo, Pattern_Type_Regex, false));
}

/*
 *		icregexeqjoinsel	- 不区分大小写的正则表达式匹配的连接选择性。
 */
Datum icregexeqjoinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternjoinsel(fcinfo, Pattern_Type_Regex_IC, false));
}

/*
 *		likejoinsel			- LIKE模式匹配的连接选择性。
 */
Datum likejoinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternjoinsel(fcinfo, Pattern_Type_Like, false));
}

/*
 *		prefixjoinsel			- 前缀运算符的连接选择性
 */
Datum prefixjoinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternjoinsel(fcinfo, Pattern_Type_Prefix, false));
}

/*
 *		iclikejoinsel			- ILIKE模式匹配的连接选择性。
 */
Datum iclikejoinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternjoinsel(fcinfo, Pattern_Type_Like_IC, false));
}

/*
 *		regexnejoinsel	- regex不匹配的连接选择性。
 */
Datum regexnejoinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternjoinsel(fcinfo, Pattern_Type_Regex, true));
}

/*
 *		icregexnejoinsel	- 不区分大小写的regex不匹配的连接选择性。
 */
Datum icregexnejoinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternjoinsel(fcinfo, Pattern_Type_Regex_IC, true));
}

/*
 *		nlikejoinsel		- LIKE模式不匹配的连接选择性。
 */
Datum nlikejoinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternjoinsel(fcinfo, Pattern_Type_Like, true));
}

/*
 *		icnlikejoinsel		- ILIKE模式不匹配的连接选择性。
 */
Datum icnlikejoinsel(PG_FUNCTION_ARGS)
{
	PG_RETURN_FLOAT8(fc_patternjoinsel(fcinfo, Pattern_Type_Like_IC, true));
}


/*-------------------------------------------------------------------------
 *
 * 模式分析函数
 *
 * 这些例程支持规划器/优化器对LIKE和正则表达式模式的分析。
 * 重要的是，它们必须与backend/regex/中的正则表达式代码和
 * backend/utils/adt/like.c中的LIKE代码一致。 
 * 此外，固定前缀的计算必须是保守的：如果我们报告的字符串长度
 * 超过了真实的固定前缀，查询可能会产生实际错误的答案，而不仅
 * 仅仅是获得一个不好的选择性估计！
 *
 *-------------------------------------------------------------------------
 */

/*
 * 提取模式的固定前缀（如果有的话）。
 *
 * *prefix被设置为一个palloc'd前缀字符串（以Const节点的形式），
 * 或设置为NULL，如果该模式没有固定前缀。
 * 如果rest_selec不为NULL，则*rest_selec被设置为余下模式的选择性
 * 估计（不含任何固定前缀）。
 * prefix Const具有与输入模式相同的类型（TEXT或BYTEA）。
 *
 * 返回值区别了没有固定前缀、部分前缀或仅匹配模式。
 */

static Pattern_Prefix_Status fc_like_fixed_prefix(Const *fc_patt_const, bool fc_case_insensitive, Oid fc_collation,
				  Const **fc_prefix_const, Selectivity *fc_rest_selec)
{
	char	   *fc_match;
	char	   *fc_patt;
	int			fc_pattlen;
	Oid			fc_typeid = fc_patt_const->consttype;
	int			fc_pos,
				fc_match_pos;
	bool		fc_is_multibyte = (pg_database_encoding_max_length() > 1);
	pg_locale_t fc_locale = 0;
	bool		fc_locale_is_c = false;

	/* 右边的常量类型为text或bytea */
	Assert(fc_typeid == BYTEAOID || fc_typeid == TEXTOID);

	if (fc_case_insensitive)
	{
		if (fc_typeid == BYTEAOID)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("case insensitive matching not supported on type bytea")));

		if (!OidIsValid(fc_collation))
		{
			/*
			 * 这通常意味着解析器无法解决隐式排序的冲突，因此以这种方式报告它。
			 */
			ereport(ERROR,
					(errcode(ERRCODE_INDETERMINATE_COLLATION),
					 errmsg("could not determine which collation to use for ILIKE"),
					 errhint("Use the COLLATE clause to set the collation explicitly.")));
		}

		/* 如果不区分大小写，我们需要区域信息 */
		if (lc_ctype_is_c(fc_collation))
			fc_locale_is_c = true;
		else
			fc_locale = pg_newlocale_from_collation(fc_collation);
	}

	if (fc_typeid != BYTEAOID)
	{
		fc_patt = TextDatumGetCString(fc_patt_const->constvalue);
		fc_pattlen = strlen(fc_patt);
	}
	else
	{
		bytea	   *fc_bstr = DatumGetByteaPP(fc_patt_const->constvalue);

		fc_pattlen = VARSIZE_ANY_EXHDR(fc_bstr);
		fc_patt = (char *) palloc(fc_pattlen);
		memcpy(fc_patt, VARDATA_ANY(fc_bstr), fc_pattlen);
		Assert((Pointer) fc_bstr == DatumGetPointer(fc_patt_const->constvalue));
	}

	fc_match = palloc(fc_pattlen + 1);
	fc_match_pos = 0;
	for (fc_pos = 0; fc_pos < fc_pattlen; fc_pos++)
	{
		/* %和_是LIKE中的通配符字符 */
		if (fc_patt[fc_pos] == '%' ||
			fc_patt[fc_pos] == '_')
			break;

		/* 反斜线转义下一个字符 */
		if (fc_patt[fc_pos] == '\\')
		{
			fc_pos++;
			if (fc_pos >= fc_pattlen)
				break;
		}

		/* 如果是区分大小写的字符，则停止（这算是一个通配符） */
		if (fc_case_insensitive &&
			fc_pattern_char_isalpha(fc_patt[fc_pos], fc_is_multibyte, fc_locale, fc_locale_is_c))
			break;

		fc_match[fc_match_pos++] = fc_patt[fc_pos];
	}

	fc_match[fc_match_pos] = '\0';

	if (fc_typeid != BYTEAOID)
		*fc_prefix_const = fc_string_to_const(fc_match, fc_typeid);
	else
		*fc_prefix_const = fc_string_to_bytea_const(fc_match, fc_match_pos);

	if (fc_rest_selec != NULL)
		*fc_rest_selec = fc_like_selectivity(&fc_patt[fc_pos], fc_pattlen - fc_pos,
									   fc_case_insensitive);

	pfree(fc_patt);
	pfree(fc_match);

	/* 在LIKE中，空模式是准确匹配！ */
	if (fc_pos == fc_pattlen)
		return Pattern_Prefix_Exact;	/* 到达模式末尾，因此是准确的 */

	if (fc_match_pos > 0)
		return Pattern_Prefix_Partial;

	return Pattern_Prefix_None;
}

static Pattern_Prefix_Status fc_regex_fixed_prefix(Const *fc_patt_const, bool fc_case_insensitive, Oid fc_collation,
				   Const **fc_prefix_const, Selectivity *fc_rest_selec)
{
	Oid			fc_typeid = fc_patt_const->consttype;
	char	   *fc_prefix;
	bool		fc_exact;

	/*
	 * 应该是不必要的，没有定义bytea regex运算符。因此，
	 * 应该注意此函数的其余部分并未对二进制（可能包含NULL的）
	 * 字符串进行安全处理。
	 */
	if (fc_typeid == BYTEAOID)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("regular-expression matching not supported on type bytea")));

	/* 使用正则表达式机制提取前缀（如果有的话） */
	fc_prefix = regexp_fixed_prefix(DatumGetTextPP(fc_patt_const->constvalue),
								 fc_case_insensitive, fc_collation,
								 &fc_exact);

	if (fc_prefix == NULL)
	{
		*fc_prefix_const = NULL;

		if (fc_rest_selec != NULL)
		{
			char	   *fc_patt = TextDatumGetCString(fc_patt_const->constvalue);

			*fc_rest_selec = fc_regex_selectivity(fc_patt, strlen(fc_patt),
											fc_case_insensitive,
											0);
			pfree(fc_patt);
		}

		return Pattern_Prefix_None;
	}

	*fc_prefix_const = fc_string_to_const(fc_prefix, fc_typeid);

	if (fc_rest_selec != NULL)
	{
		if (fc_exact)
		{
			/* 精确匹配，因此没有额外的选择性 */
			*fc_rest_selec = 1.0;
		}
		else
		{
			char	   *fc_patt = TextDatumGetCString(fc_patt_const->constvalue);

			*fc_rest_selec = fc_regex_selectivity(fc_patt, strlen(fc_patt),
											fc_case_insensitive,
											strlen(fc_prefix));
			pfree(fc_patt);
		}
	}

	pfree(fc_prefix);

	if (fc_exact)
		return Pattern_Prefix_Exact;	/* 模式指定精确匹配 */
	else
		return Pattern_Prefix_Partial;
}

static Pattern_Prefix_Status fc_pattern_fixed_prefix(Const *fc_patt, Pattern_Type fc_ptype, Oid fc_collation,
					 Const **fc_prefix, Selectivity *fc_rest_selec)
{
	Pattern_Prefix_Status fc_result;

	switch (fc_ptype)
	{
		case Pattern_Type_Like:
			fc_result = fc_like_fixed_prefix(fc_patt, false, fc_collation,
									   fc_prefix, fc_rest_selec);
			break;
		case Pattern_Type_Like_IC:
			fc_result = fc_like_fixed_prefix(fc_patt, true, fc_collation,
									   fc_prefix, fc_rest_selec);
			break;
		case Pattern_Type_Regex:
			fc_result = fc_regex_fixed_prefix(fc_patt, false, fc_collation,
										fc_prefix, fc_rest_selec);
			break;
		case Pattern_Type_Regex_IC:
			fc_result = fc_regex_fixed_prefix(fc_patt, true, fc_collation,
										fc_prefix, fc_rest_selec);
			break;
		case Pattern_Type_Prefix:
			/* 前缀类型的工作是微不足道的。 */
			fc_result = Pattern_Prefix_Partial;
			*fc_prefix = makeConst(fc_patt->consttype,
								fc_patt->consttypmod,
								fc_patt->constcollid,
								fc_patt->constlen,
								datumCopy(fc_patt->constvalue,
										  fc_patt->constbyval,
										  fc_patt->constlen),
								fc_patt->constisnull,
								fc_patt->constbyval);
			if (fc_rest_selec != NULL)
				*fc_rest_selec = 1.0;	/* all */
			break;
		default:
			elog(ERROR, "unrecognized ptype: %d", (int) fc_ptype);
			fc_result = Pattern_Prefix_None;	/* 保持编译器安静 */
			break;
	}
	return fc_result;
}

/*
 * 估计模式匹配的固定前缀的选择性。
 *
 * 固定前缀 "foo" 被估计为表达式 "variable >= 'foo' AND variable < 'fop'" 的选择性。
 *
 * 选择性估计是关于列人口部分的，与直方图相对应 --- 调用者必须将其与
 * MCV和NULL的信息结合起来。
 *
 * 我们使用给定的比较运算符和排序来进行估计。
 * 给定的变量和Const必须是相关的数据类型。
 *
 * XXX 注意：即使区域设置使我们不能依赖上界字符串，我们也使用
 * 上界来估算运算符选择性。反正选择性只需要大致正确，因此似乎使用
 * 上界代码比不使用要更有用。
 */
static Selectivity fc_prefix_selectivity(PlannerInfo *fc_root, VariableStatData *fc_vardata,
				   Oid fc_eqopr, Oid fc_ltopr, Oid fc_geopr,
				   Oid fc_collation,
				   Const *fc_prefixcon)
{
	Selectivity fc_prefixsel;
	FmgrInfo	fc_opproc;
	Const	   *fc_greaterstrcon;
	Selectivity fc_eq_sel;

	/* 估计“x >= prefix”的选择性 */
	fmgr_info(get_opcode(fc_geopr), &fc_opproc);

	fc_prefixsel = ineq_histogram_selectivity(fc_root, fc_vardata,
										   fc_geopr, &fc_opproc, true, true,
										   fc_collation,
										   fc_prefixcon->constvalue,
										   fc_prefixcon->consttype);

	if (fc_prefixsel < 0.0)
	{
		/* 没有直方图...返回一个合适的默认估计 */
		return DEFAULT_MATCH_SEL;
	}

	/*
	 * 如果我们可以创建一个比前缀更大的字符串，比如 "x < greaterstr"。
	 */
	fmgr_info(get_opcode(fc_ltopr), &fc_opproc);
	fc_greaterstrcon = fc_make_greater_string(fc_prefixcon, &fc_opproc, fc_collation);
	if (fc_greaterstrcon)
	{
		Selectivity fc_topsel;

		fc_topsel = ineq_histogram_selectivity(fc_root, fc_vardata,
											fc_ltopr, &fc_opproc, false, false,
											fc_collation,
											fc_greaterstrcon->constvalue,
											fc_greaterstrcon->consttype);

		/* ineq_histogram_selectivity之前有效，现在不应该失败 */
		Assert(fc_topsel >= 0.0);

		/*
		 * 以与范围查询相同的方式合并两个选择性
		 * （见clauselist_selectivity()）。注意，我们不需要担心NULL的双重排除，
		 * 因为ineq_histogram_selectivity根本不计算这些。
		 */
		fc_prefixsel = fc_topsel + fc_prefixsel - 1.0;
	}

	/*
	 * 如果前缀很长，则两个边界值可能太接近，
	 * 使直方图无法有效地区分它们，导致零估计（加上舍入误差）。
	 * 为了避免返回一个荒谬的小估计，计算“variable = 'foo'”的选择性估计，
	 * 并将其限制为此值。（显然，最终估计应该至少如此。）
	 *
	 * 即使我们无法创建更大的字符串，也应用这种处理。
	 * 那种情况表明前缀接近最大可能值，因此
	 * 可能超出了直方图的范围，因此我们可能从>=条件中得到了一个非常小的
	 * 估计；所以我们仍然需要限制。
	 */
	fc_eq_sel = var_eq_const(fc_vardata, fc_eqopr, fc_collation, fc_prefixcon->constvalue,
						  false, true, false);

	fc_prefixsel = Max(fc_prefixsel, fc_eq_sel);

	return fc_prefixsel;
}




#define FIXED_CHAR_SEL	0.20	/* 大约 1/5 */
#define CHAR_RANGE_SEL	0.25
#define ANY_CHAR_SEL	0.9		/* 不为 1，因为它不会匹配字符串结尾 */
#define FULL_WILDCARD_SEL 5.0
#define PARTIAL_WILDCARD_SEL 2.0

static Selectivity fc_like_selectivity(const char *fc_patt, int fc_pattlen, bool fc_case_insensitive)
{
	Selectivity fc_sel = 1.0;
	int			fc_pos;

	/* 跳过任何前导通配符；它已经考虑在初始选择中 */
	for (fc_pos = 0; fc_pos < fc_pattlen; fc_pos++)
	{
		if (fc_patt[fc_pos] != '%' && fc_patt[fc_pos] != '_')
			break;
	}

	for (; fc_pos < fc_pattlen; fc_pos++)
	{
		/* %和_是LIKE中的通配符字符 */
		if (fc_patt[fc_pos] == '%')
			fc_sel *= FULL_WILDCARD_SEL;
		else if (fc_patt[fc_pos] == '_')
			fc_sel *= ANY_CHAR_SEL;
		else if (fc_patt[fc_pos] == '\\')
		{
			/* 反斜杠引号下一个字符 */
			fc_pos++;
			if (fc_pos >= fc_pattlen)
				break;
			fc_sel *= FIXED_CHAR_SEL;
		}
		else
			fc_sel *= FIXED_CHAR_SEL;
	}
	/* 如果有多个通配符，选择可能大于 1 */
	if (fc_sel > 1.0)
		fc_sel = 1.0;
	return fc_sel;
}

static Selectivity fc_regex_selectivity_sub(const char *fc_patt, int fc_pattlen, bool fc_case_insensitive)
{
	Selectivity fc_sel = 1.0;
	int			fc_paren_depth = 0;
	int			fc_paren_pos = 0;	/* 虚拟初始化以保持编译器安静 */
	int			fc_pos;

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	for (fc_pos = 0; fc_pos < fc_pattlen; fc_pos++)
	{
		if (fc_patt[fc_pos] == '(')
		{
			if (fc_paren_depth == 0)
				fc_paren_pos = fc_pos;	/* 记住括号项的开始 */
			fc_paren_depth++;
		}
		else if (fc_patt[fc_pos] == ')' && fc_paren_depth > 0)
		{
			fc_paren_depth--;
			if (fc_paren_depth == 0)
				fc_sel *= fc_regex_selectivity_sub(fc_patt + (fc_paren_pos + 1),
											 fc_pos - (fc_paren_pos + 1),
											 fc_case_insensitive);
		}
		else if (fc_patt[fc_pos] == '|' && fc_paren_depth == 0)
		{
			/*
			 * 如果在模式的括号级别 0 中存在未引用的 |，我们有
			 * 多个选项；计算它们的概率总和。
			 */
			fc_sel += fc_regex_selectivity_sub(fc_patt + (fc_pos + 1),
										 fc_pattlen - (fc_pos + 1),
										 fc_case_insensitive);
			break;				/* 模式的其余部分现在被处理 */
		}
		else if (fc_patt[fc_pos] == '[')
		{
			bool		fc_negclass = false;

			if (fc_patt[++fc_pos] == '^')
			{
				fc_negclass = true;
				fc_pos++;
			}
			if (fc_patt[fc_pos] == ']')	/* 类的开头处的 ']' 不是特殊的 */
				fc_pos++;
			while (fc_pos < fc_pattlen && fc_patt[fc_pos] != ']')
				fc_pos++;
			if (fc_paren_depth == 0)
				fc_sel *= (fc_negclass ? (1.0 - CHAR_RANGE_SEL) : CHAR_RANGE_SEL);
		}
		else if (fc_patt[fc_pos] == '.')
		{
			if (fc_paren_depth == 0)
				fc_sel *= ANY_CHAR_SEL;
		}
		else if (fc_patt[fc_pos] == '*' ||
				 fc_patt[fc_pos] == '?' ||
				 fc_patt[fc_pos] == '+')
		{
			/* 应该更聪明地处理量词... */
			if (fc_paren_depth == 0)
				fc_sel *= PARTIAL_WILDCARD_SEL;
		}
		else if (fc_patt[fc_pos] == '{')
		{
			while (fc_pos < fc_pattlen && fc_patt[fc_pos] != '}')
				fc_pos++;
			if (fc_paren_depth == 0)
				fc_sel *= PARTIAL_WILDCARD_SEL;
		}
		else if (fc_patt[fc_pos] == '\\')
		{
			/* 反斜杠引号下一个字符 */
			fc_pos++;
			if (fc_pos >= fc_pattlen)
				break;
			if (fc_paren_depth == 0)
				fc_sel *= FIXED_CHAR_SEL;
		}
		else
		{
			if (fc_paren_depth == 0)
				fc_sel *= FIXED_CHAR_SEL;
		}
	}
	/* 如果有多个通配符，选择可能大于 1 */
	if (fc_sel > 1.0)
		fc_sel = 1.0;
	return fc_sel;
}

static Selectivity fc_regex_selectivity(const char *fc_patt, int fc_pattlen, bool fc_case_insensitive,
				  int fc_fixed_prefix_len)
{
	Selectivity fc_sel;

	/* 如果模式不以 $ 结尾，则认为它有一个尾随通配符 */
	if (fc_pattlen > 0 && fc_patt[fc_pattlen - 1] == '$' &&
		(fc_pattlen == 1 || fc_patt[fc_pattlen - 2] != '\\'))
	{
		/* 有尾随 $ */
		fc_sel = fc_regex_selectivity_sub(fc_patt, fc_pattlen - 1, fc_case_insensitive);
	}
	else
	{
		/* 没有尾随 $ */
		fc_sel = fc_regex_selectivity_sub(fc_patt, fc_pattlen, fc_case_insensitive);
		fc_sel *= FULL_WILDCARD_SEL;
	}

	/*
	 * 如果有固定前缀，则降低其选择性。我们必须谨慎这里，因为非常长的前缀可能导致
	 * pow 的结果下溢至零（在这种情况下 "sel" 可能也是如此）。
	 */
	if (fc_fixed_prefix_len > 0)
	{
		double		fc_prefixsel = pow(FIXED_CHAR_SEL, fc_fixed_prefix_len);

		if (fc_prefixsel > 0.0)
			fc_sel /= fc_prefixsel;
	}

	/* 确保结果保持在范围内 */
	CLAMP_PROBABILITY(fc_sel);
	return fc_sel;
}

/*
 * 检查字符是否为字母（因而，受大小写折叠影响）
 *
 * 在多字节字符集或使用 ICU 时，我们无法使用 isalpha，并且似乎也没有必要尝试转换为 wchar_t 以使用 iswalpha 或 u_isalpha。
 * 相反，只需假设任何非 ASCII 字符可能是大小写变化的，并且
 * 硬编码了解哪些 ASCII 字符是字母。
 */
static int fc_pattern_char_isalpha(char fc_c, bool fc_is_multibyte,
					 pg_locale_t fc_locale, bool fc_locale_is_c)
{
	if (fc_locale_is_c)
		return (fc_c >= 'A' && fc_c <= 'Z') || (fc_c >= 'a' && fc_c <= 'z');
	else if (fc_is_multibyte && IS_HIGHBIT_SET(fc_c))
		return true;
	else if (fc_locale && fc_locale->provider == COLLPROVIDER_ICU)
		return IS_HIGHBIT_SET(fc_c) ||
			(fc_c >= 'A' && fc_c <= 'Z') || (fc_c >= 'a' && fc_c <= 'z');
#ifdef HAVE_LOCALE_T
	else if (fc_locale && fc_locale->provider == COLLPROVIDER_LIBC)
		return isalpha_l((unsigned char) fc_c, fc_locale->info.lt);
#endif
	else
		return isalpha((unsigned char) fc_c);
}


/*
 * 对于 bytea，增量函数只需递增当前字节
 * （无需担心多字节字符）。
 */
static bool fc_byte_increment(unsigned char *fc_ptr, int fc_len)
{
	if (*fc_ptr >= 255)
		return false;
	(*fc_ptr)++;
	return true;
}

/*
 * 尝试生成一个大于给定字符串或任何
 * 以其为前缀的字符串。如果成功，返回一个 palloc'd 字符串
 * 以 Const 节点的形式；否则返回 NULL。
 *
 * 调用者必须提供适当的“小于”比较函数
 * 用于测试字符串，以及使用的排序规则。
 *
 * 这里的关键要求是，给定前缀字符串，比如 "foo"， 
 * 我们必须能够生成另一个字符串 "fop"，这个字符串大于
 * 所有以 "foo" 开头的字符串 "foobar"。我们可以测试我们是否
 * 生成了一个大于前缀字符串的字符串，但在非 C 排序中
 * 这并不能保证字符串的扩展不会在其后排序；例如，"foo " 小于 "foo!"，
 * 但不清楚“字典”排序顺序是否会将 "foo!" 视为小于 "foo bar"。注意：因此，此函数应该仅在
 * 在非 C 排序的情况下用于估算目的。
 *
 * 为了尽量捕获大多数情况下扩展字符串可能在结果值之前排序，
 * 我们确定哪个字符串 "Z"、"z"、"y" 和 "9" 在排序中被视为最大，
 * 并在尝试寻找更大的字符串之前将其附加到给定的前缀。
 *
 * 为了搜索更大的字符串，我们反复“递增”最右边
 * 字符，使用特定于编码的字符递增函数。
 * 当无法再递增最后一个字符时，我们截断该字符并开始递增倒数第二个字符。
 * 例如，如果 "z" 是排序顺序中的最后一个字符，那么我们
 * 可以生成 "foo" 作为大于 "fonz" 的字符串。
 *
 * 在最坏的情况下，这可能会相当慢，但在大多数情况下
 * 我们不会尝试超过一两个字符串就能成功。
 *
 * 请注意，字符递增器在生成每个可能的字符代码时不能过于严格，
 * 因为在某些情况下，获得更大字符串的唯一方法是递增之前的字符位置。
 * 因此我们不想花太多时间尝试最后一个位置的每个可能字符代码。
 * 一个好的经验法则是确保我们对 K 字节字符不会尝试超过 256*K 的值
 * （并且绝对不要尝试 256^K，这是全面搜索接近的值）。
 */
static Const * fc_make_greater_string(const Const *fc_str_const, FmgrInfo *fc_ltproc, Oid fc_collation)
{
	Oid			fc_datatype = fc_str_const->consttype;
	char	   *fc_workstr;
	int			fc_len;
	Datum		fc_cmpstr;
	char	   *fc_cmptxt = NULL;
	mbcharacter_incrementer fc_charinc;

	/*
	 * 获取一个可修改的前缀字符串的 C 字符串格式副本，并设置
	 * 将用于比较的字符串作为 Datum。在 C 区域设置中，这可以是给定的前缀字符串，
	 * 否则我们需要添加后缀。类型 BYTEA 按字节排序，因此它也从不需要后缀。
	 */
	if (fc_datatype == BYTEAOID)
	{
		bytea	   *fc_bstr = DatumGetByteaPP(fc_str_const->constvalue);

		fc_len = VARSIZE_ANY_EXHDR(fc_bstr);
		fc_workstr = (char *) palloc(fc_len);
		memcpy(fc_workstr, VARDATA_ANY(fc_bstr), fc_len);
		Assert((Pointer) fc_bstr == DatumGetPointer(fc_str_const->constvalue));
		fc_cmpstr = fc_str_const->constvalue;
	}
	else
	{
		if (fc_datatype == NAMEOID)
			fc_workstr = DatumGetCString(DirectFunctionCall1(nameout,
														  fc_str_const->constvalue));
		else
			fc_workstr = TextDatumGetCString(fc_str_const->constvalue);
		fc_len = strlen(fc_workstr);
		if (lc_collate_is_c(fc_collation) || fc_len == 0)
			fc_cmpstr = fc_str_const->constvalue;
		else
		{
			/* 如果是第一次通过，确定要使用的后缀 */
			static char fc_suffixchar = 0;
			static Oid	fc_suffixcollation = 0;

			if (!fc_suffixchar || fc_suffixcollation != fc_collation)
			{
				char	   *fc_best;

				fc_best = "Z";
				if (varstr_cmp(fc_best, 1, "z", 1, fc_collation) < 0)
					fc_best = "z";
				if (varstr_cmp(fc_best, 1, "y", 1, fc_collation) < 0)
					fc_best = "y";
				if (varstr_cmp(fc_best, 1, "9", 1, fc_collation) < 0)
					fc_best = "9";
				fc_suffixchar = *fc_best;
				fc_suffixcollation = fc_collation;
			}

			/* 并构建要比较的字符串 */
			if (fc_datatype == NAMEOID)
			{
				fc_cmptxt = palloc(fc_len + 2);
				memcpy(fc_cmptxt, fc_workstr, fc_len);
				fc_cmptxt[fc_len] = fc_suffixchar;
				fc_cmptxt[fc_len + 1] = '\0';
				fc_cmpstr = PointerGetDatum(fc_cmptxt);
			}
			else
			{
				fc_cmptxt = palloc(VARHDRSZ + fc_len + 1);
				SET_VARSIZE(fc_cmptxt, VARHDRSZ + fc_len + 1);
				memcpy(VARDATA(fc_cmptxt), fc_workstr, fc_len);
				*(VARDATA(fc_cmptxt) + fc_len) = fc_suffixchar;
				fc_cmpstr = PointerGetDatum(fc_cmptxt);
			}
		}
	}

	/* 选择适当的字符递增函数 */
	if (fc_datatype == BYTEAOID)
		fc_charinc = fc_byte_increment;
	else
		fc_charinc = pg_database_encoding_character_incrementer();

	/* 并搜索... */
	while (fc_len > 0)
	{
		int			fc_charlen;
		unsigned char *fc_lastchar;

		/* 确定最后一个字符——对于 bytea，仅最后一个字节 */
		if (fc_datatype == BYTEAOID)
			fc_charlen = 1;
		else
			fc_charlen = fc_len - pg_mbcliplen(fc_workstr, fc_len, fc_len - 1);
		fc_lastchar = (unsigned char *) (fc_workstr + fc_len - fc_charlen);

		
/*
		 * 尝试通过递增最后一个字符来生成一个更大的字符串
		 * （对于 BYTEA，我们将每个字节视为一个字符）。
		 *
		 * 注意：递增函数在生成一个符合编码的新字符时，预期返回 true，
		 * 否则返回 false。在返回 false 时，字符的内容未定义。
		 */
		while (fc_charinc(fc_lastchar, fc_charlen))
		{
			Const	   *fc_workstr_const;

			if (fc_datatype == BYTEAOID)
				fc_workstr_const = fc_string_to_bytea_const(fc_workstr, fc_len);
			else
				fc_workstr_const = fc_string_to_const(fc_workstr, fc_datatype);

			if (DatumGetBool(FunctionCall2Coll(fc_ltproc,
											   fc_collation,
											   fc_cmpstr,
											   fc_workstr_const->constvalue)))
			{
				/* 成功生成了一个比 cmpstr 更大的字符串 */
				if (fc_cmptxt)
					pfree(fc_cmptxt);
				pfree(fc_workstr);
				return fc_workstr_const;
			}

			/* 不好，释放不可用的值并重试 */
			pfree(DatumGetPointer(fc_workstr_const->constvalue));
			pfree(fc_workstr_const);
		}

		/*
		 * 在这里没有运气，所以截断最后一个字符并尝试
		 * 递增下一个字符。
		 */
		fc_len -= fc_charlen;
		fc_workstr[fc_len] = '\0';
	}

	/* 失败... */
	if (fc_cmptxt)
		pfree(fc_cmptxt);
	pfree(fc_workstr);

	return NULL;
}

/*
 * 从 C 字符串生成适当类型的 Datum。
 * 注意，所有支持的类型都是按引用传递的，因此如果不再需要
 * 返回的值，应该进行 pfree。
 */
static Datum fc_string_to_datum(const char *fc_str, Oid fc_datatype)
{
	Assert(fc_str != NULL);

	/*
	 * 我们稍微作弊一下，假设 CStringGetTextDatum() 也适用于
	 * bpchar 和 varchar 常量...
	 */
	if (fc_datatype == NAMEOID)
		return DirectFunctionCall1(namein, CStringGetDatum(fc_str));
	else if (fc_datatype == BYTEAOID)
		return DirectFunctionCall1(byteain, CStringGetDatum(fc_str));
	else
		return CStringGetTextDatum(fc_str);
}

/*
 * 从 C 字符串生成适当类型的 Const 节点。
 */
static Const * fc_string_to_const(const char *fc_str, Oid fc_datatype)
{
	Datum		fc_conval = fc_string_to_datum(fc_str, fc_datatype);
	Oid			fc_collation;
	int			fc_constlen;

	/*
	 * 我们只需要在这里支持一些数据类型，因此硬编码属性
	 * 而不是花费时间进行目录查找。
	 */
	switch (fc_datatype)
	{
		case TEXTOID:
		case VARCHAROID:
		case BPCHAROID:
			fc_collation = DEFAULT_COLLATION_OID;
			fc_constlen = -1;
			break;

		case NAMEOID:
			fc_collation = C_COLLATION_OID;
			fc_constlen = NAMEDATALEN;
			break;

		case BYTEAOID:
			fc_collation = InvalidOid;
			fc_constlen = -1;
			break;

		default:
			elog(ERROR, "unexpected datatype in string_to_const: %u",
				 fc_datatype);
			return NULL;
	}

	return makeConst(fc_datatype, -1, fc_collation, fc_constlen,
					 fc_conval, false, false);
}

/*
 * 从二进制 C 字符串和长度生成 bytea 类型的 Const 节点。
 */
static Const * fc_string_to_bytea_const(const char *fc_str, size_t fc_str_len)
{
	bytea	   *fc_bstr = palloc(VARHDRSZ + fc_str_len);
	Datum		fc_conval;

	memcpy(VARDATA(fc_bstr), fc_str, fc_str_len);
	SET_VARSIZE(fc_bstr, VARHDRSZ + fc_str_len);
	fc_conval = PointerGetDatum(fc_bstr);

	return makeConst(BYTEAOID, -1, InvalidOid, -1, fc_conval, false, false);
}
